﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Brain
{
    /// <summary>
    /// Class for Animating World Objects
    /// </summary>
    public class Animation_WorldObject : IBaseAnimation
    {
        /// <summary>
        /// All the Frames this Animation class have
        /// </summary>
        public KeyFrame_WorldObject[] Frames { get; protected set; }

        /// <summary>
        /// If the animation should Loop
        /// </summary>
        public bool Loop { get; set; }

        private int currentFrame;
        /// <summary>
        /// The Current Frame this Animation is at.
        /// Be aware that setting this value WILL Update the
        /// Animation
        /// </summary>
        public int CurrentFrame
        {
            get { return currentFrame; }
            set
            {
                currentFrame = value;
                Update();
            }
        }

        /// <summary>
        /// The Length of the Animation. Can change if the Animation is looping, due
        /// to the Loop Restart Time
        /// </summary>
        public int AnimationEnd
        {
            get
            {
                if (Loop)
                {
                    return totalTime + LoopRestartTime;
                }
                else
                {
                    return totalTime;
                }
            }
        }

        /// <summary>
        /// The Frame the Animation should start
        /// </summary>
        public int StartFrame { get; set; }

        /// <summary>
        /// The WorldObject we are modifying
        /// </summary>
        public WorldObject WorldObject;

        /// <summary>
        /// The Time it takes for the Loop to restart
        /// </summary>
        public int LoopRestartTime = 50;

        /// <summary>
        /// Creates a new Animation_Vector2. Constructor used for Deserializing ONLY, use the another
        /// </summary>
        public Animation_WorldObject()
        {
            ShouldSerialize = true;
        }

        /// <summary>
        /// Creates a new Animation_Sprite
        /// </summary>
        public Animation_WorldObject(WorldObject obj)
        {
            ShouldSerialize = true;
            this.WorldObject = obj;
        }

        private List<KeyFrame_WorldObject> frames;

        private int totalTime;

        public void SubmitFrame(Vector3? Position, Vector3? Rotation, Vector3? Scale, int Time)
        {
            KeyFrame_WorldObject frame = new KeyFrame_WorldObject(Position,
                Rotation, Scale, Time);
            SubmitFrame(frame);
        }
        public void SubmitFrame(KeyFrame_WorldObject frame)
        {
            if (frames == null)
            {
                frames = new List<KeyFrame_WorldObject>();
            }
            if (frame.Time > totalTime)
            {
                totalTime = frame.Time;
            }

            this.frames.Add(frame);

            isOptimized = false;
        }

        private bool isOptimized = false;
        /// <summary>
        /// If True, the Array of Frames is ordered by time,
        /// and thus the Array of Frames is made, not the list
        /// </summary>
        public bool IsOptimized
        {
            get { return isOptimized; }
        }

        public void Optimize()
        {
            this.frames.Sort(CompareKeyFrames);
            this.Frames = frames.ToArray();

            isOptimized = true;
        }
        private int CompareKeyFrames(KeyFrame_WorldObject frame1, KeyFrame_WorldObject frame2)
        {
            return frame1.Time.CompareTo(frame2.Time);
        }

        private void Update()
        {
            if (Frames == null || !isOptimized)
            {
                Optimize();
            }

            bool isNull;
            int index;
            KeyFrame_WorldObject Frame = FindLastKeyFrame(CurrentFrame, out isNull, out index);

            if (!isNull)
            {
                bool nextNull;
                bool isRestart;
                KeyFrame_WorldObject Next = FindNextFrame(index, out nextNull, out isRestart);

                if (nextNull)
                {
                    // Nothing to Interpolate, just set the Position to the Frame pos                    
                    if (Frame.Position != null)
                    {
                        WorldObject.Position = Frame.Position.Value;
                    }
                    if (Frame.RotationRadians != null)
                    {
                        WorldObject.RotationRadians = Frame.RotationRadians.Value;
                    }
                    if (Frame.Scale != null)
                    {
                        WorldObject.Scale = Frame.Scale.Value;
                    }
                }
                else
                {
                    if (isRestart)
                    {
                        float amount = ((float)CurrentFrame - Frame.Time) / (float)LoopRestartTime;
                        LerpWorldObject(amount, Frame, Next);
                    }
                    else
                    {
                        float amount = ((float)CurrentFrame - Next.Time) / ((float)Frame.Time - Next.Time);
                        LerpWorldObject(amount, Next, Frame);
                    }
                }
            }
        }

        protected void LerpWorldObject(float amount, KeyFrame_WorldObject last, KeyFrame_WorldObject next)
        {
            if (last.Position != null &&
                next.Position != null)
            {
                Vector3 lastValue = last.Position.Value;
                Vector3 nextValue = next.Position.Value;
                WorldObject.Position = Vector3Util.Lerp(lastValue, nextValue, amount);
            }
            if (last.RotationRadians != null &&
                next.RotationRadians != null)
            {
                Vector3 lastValue = last.RotationRadians.Value;
                Vector3 nextValue = next.RotationRadians.Value;
                WorldObject.RotationRadians = Vector3Util.Lerp(lastValue, nextValue, amount);
            }
            if (last.Scale != null &&
                next.Scale != null)
            {
                Vector3 lastValue = last.Scale.Value;
                Vector3 nextValue = next.Scale.Value;
                WorldObject.Scale = Vector3Util.Lerp(lastValue, nextValue, amount);
            }
        }

        private float currentLoopFrame;
        public void UpdateLoop(float frameDif)
        {
            currentLoopFrame += frameDif;

            while (currentLoopFrame > this.AnimationEnd)
            {
                currentLoopFrame -= this.AnimationEnd;
            }

            int current = (int)currentLoopFrame;
            if (currentFrame != current)
            {
                this.CurrentFrame = current;
            }
        }
        public void Update(int frame)
        {
            this.CurrentFrame = frame;
        }

        protected KeyFrame_WorldObject FindLastKeyFrame(int nextTime, out bool isNull, out int index)
        {
            object lastFoundFrame = null;
            KeyFrame_WorldObject lastFoundF = default(KeyFrame_WorldObject);

            isNull = true;
            index = -1;

            for (int i = 0; i < Frames.Length; i++)
            {
                KeyFrame_WorldObject fr = Frames[i];
                if (lastFoundFrame == null)
                {
                    if (fr.Time < nextTime)
                    {
                        index = i;
                        lastFoundF = fr;
                        lastFoundFrame = lastFoundF;

                        isNull = false;
                    }
                }
                else
                {
                    if (fr.Time <= nextTime &&
                        fr.Time > lastFoundF.Time)
                    {
                        index = i;
                        lastFoundF = fr;
                        lastFoundFrame = fr;
                    }
                }
            }


            return lastFoundF;
        }
        protected KeyFrame_WorldObject FindNextFrame(int index, out bool isNull, out bool isRestart)
        {
            int nextIndex = index + 1;
            if (this.Frames.Length > nextIndex)
            {
                isNull = false;
                isRestart = false;
                return this.Frames[nextIndex];
            }

            if (Loop)
            {
                // Pick the first frame
                isNull = false;
                isRestart = true;
                return this.Frames[0];
            }

            isNull = true;
            isRestart = false;
            return default(KeyFrame_WorldObject);
        }

        public bool ShouldSerialize { get; set; }
        public void SerializeData(SerializationData parentData, string add)
        {
            if (Frames == null | !isOptimized)
            {
                Optimize();
            }

            parentData.AddData("Anim_WorldObject.Frames", this.Frames.Length, add);
            for (int i = 0; i < this.Frames.Length; i++)
            {
                KeyFrame_WorldObject Frame = this.Frames[i];
                parentData.AddData("Anim_WorldObject.FrameTime" + i, Frame.Time, add);

                if (Frame.Position != null)
                {
                    parentData.AddData("Anim_WorldObject.FramePosition" + i, Frame.Position.Value, add);
                }
                if (Frame.RotationRadians != null)
                {
                    parentData.AddData("Anim_WorldObject.FrameRotationRadians" + i, Frame.RotationRadians.Value, add);
                }
                if (Frame.Scale != null)
                {
                    parentData.AddData("Anim_WorldObject.FrameScale" + i, Frame.Scale.Value, add);
                }
            }
        }

        public void DeserializeData(SerializationData parentData, string add)
        {
            int frames = parentData.GetData<int>("Anim_WorldObject.Frames", add);
            for (int i = 0; i < frames; i++)
            {
                int time = parentData.GetData<int>("Anim_WorldObject.FrameTime" + i, add);

                Vector3? Position = parentData.GetData<Vector3?>("Anim_WorldObject.FramePosition" + i, add);
                Vector3? Rotation = parentData.GetData<Vector3?>("Anim_WorldObject.FrameRotationRadians" + i, add);
                Vector3? Scale = parentData.GetData<Vector3?>("Anim_WorldObject.FrameScale" + i, add);

                KeyFrame_WorldObject frame = new KeyFrame_WorldObject();
                frame.Position = Position;
                frame.RotationRadians = Rotation;
                frame.Scale = Scale;
                frame.Time = time;

                SubmitFrame(frame);
            }

            Optimize();
        }


        public void DeserializedAll()
        {
        }
    }
}
